import numpy as np
class PCA1:
    def __init__(self,n_components):
        """初始化PCA"""
        assert n_components>=1#主成分个数大于等于1
        self.n_components=n_components
        self.components_=None
    
    def fit(self,X,eta=0.01,n_iters=1e4):
        """获取数据前n个主成分"""
        assert self.n_components<=X.shape[1]
        def demean(x):
            return x-np.mean(x)
        
        def f(w,x):#目标函数（VAr）
            return np.sum(x.dot(w)**2)/len(x)
        
        def df_math(w,x):#梯度函数
            return x.T.dot(x.dot(w))*2./len(x)
        
        def direction(w):#计算单位向量
            return w/np.linalg.norm(w)#向量/向量的模---->使其模长为1
        
        def first_component(x,initial_w,eta,n_iters=1e4,epsilon=1e-8):#梯度上升函数
            w=direction(initial_w)#使初始向量为方向向量，模长为1
            cur_iter=0
            while cur_iter<n_iters:
                gradient=df_math(w,x)
                last_w=w
                w=w+eta*gradient
                w=direction(w)#向量转变成方向向量，模长为1
                if (abs(f(w,x)-f(last_w,x))<epsilon):
                    break
                cur_iter+=1
            return w
        

        X_pca=demean(X)
        """X*WT=X'。X为(m,n),WT为(n,k)。components_为W(k,n),每一行代表一个主成分。k表示要降到的维度  """
        self.components_=np.empty(shape=(self.n_components,X.shape[1]))#初始化空矩阵
        for i in range(self.n_components):
            initial_w=np.random.random(X_pca.shape[1])#初始化搜索矩阵
            w=first_component(X_pca,initial_w,eta,n_iters)#处理一行数据
            self.components_[i,:]=w
            X_pca=X_pca-X_pca.dot(w).reshape(-1,1)*w#进入下一个主成分
        return self

    def transform(self,X):
        """将给定的X，映射到各个主成分中【X(m,n);components_(k,n)】"""
        assert X.shape[1]==self.components_.shape[1]#保证特征个数相同
        return X.dot(self.components_.T)
    
    def inverse_transform(self,X):
        """给定的X(低维度),反向映射回原来的特征空间【X(m,k);components_(k,n)】"""
        assert X.shape[1]==self.components_.shape[0]

        return X.dot(self.components_)

    def __repr__(self):
        return "PCA(COMPONENTS=%d)"%self.n_components